using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using DataService.Helper;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Client.ComplexTypes;
using Opc.Ua.Client.Controls;
using Opc.Ua.Configuration;
using OpcuaClient.Model;
using OpcuaClient.Service;
using static System.Collections.Specialized.BitVector32;

namespace Opc.Ua.Sample.Service
{
    public class OpcConnectService
    {
        LogHelper LogHelper = new LogHelper("OpcuaClient");
        public CollectServer Server;

        /// <summary>
        /// 结构体业务逻辑
        /// </summary>
        public List<ConstructService> ConstructServiceList { get; set; }
        /// <summary>
        /// 采集点位业务逻辑
        /// </summary>
        public List<CollectReadService> CollectionServiceList { get; set; }
        /// <summary>
        /// 采集点位写入逻辑
        /// </summary>
        public List<CollectWriteService> CollectionWriteServiceList { get; set; }

        #region Private Fields
        private ApplicationConfiguration m_configuration;
        private Session m_session;
        private SessionReconnectHandler m_reconnectHandler;
        private CertificateValidationEventHandler m_CertificateValidation;
        private EventHandler m_ReconnectComplete;
        private EventHandler m_ReconnectStarting;
        private EventHandler m_KeepAliveComplete;
        private EventHandler m_ConnectComplete;
        private Dictionary<Uri, EndpointDescription> m_endpoints;
        private event EventHandler m_ConnectEvent;
        public event EventHandler ConnectEvent { add { m_ConnectEvent += value; } remove { m_ConnectEvent -= value; } }
        private event EventHandler m_ConnectStatusEvent;
        public event EventHandler ConnectStatusEvent { add { m_ConnectStatusEvent += value; } remove { m_ConnectStatusEvent -= value; } }

        /// <summary>
        /// The currently active session. 
        /// </summary>
        public Session Session
        {
            get
            {
                return m_session;
            }
        }

        /// <summary>
        /// The number of seconds between reconnect attempts (0 means reconnect is disabled).
        /// </summary>
        public int ReconnectPeriod { get; set; } = DefaultReconnectPeriod;

        /// <summary>
        /// The discover timeout in ms.
        /// </summary>
        public int DiscoverTimeout { get; set; } = DefaultDiscoverTimeout;

        /// <summary>
        /// The session timeout in ms.
        /// </summary>
        public uint SessionTimeout { get; set; } = DefaultSessionTimeout;

        /// <summary>
        /// Raised when a good keep alive from the server arrives.
        /// </summary>
        public event EventHandler KeepAliveComplete
        {
            add { m_KeepAliveComplete += value; }
            remove { m_KeepAliveComplete -= value; }
        }

        /// <summary>
        /// Raised when a reconnect operation starts.
        /// </summary>
        public event EventHandler ReconnectStarting
        {
            add { m_ReconnectStarting += value; }
            remove { m_ReconnectStarting -= value; }
        }

        /// <summary>
        /// Raised when a reconnect operation completes.
        /// </summary>
        public event EventHandler ReconnectComplete
        {
            add { m_ReconnectComplete += value; }
            remove { m_ReconnectComplete -= value; }
        }

        /// <summary>
        /// Raised after successfully connecting to or disconnecing from a server.
        /// </summary>
        public event EventHandler ConnectComplete
        {
            add { m_ConnectComplete += value; }
            remove { m_ConnectComplete -= value; }
        }
        public bool Status { get; set; }
        #endregion
        /// <summary>
        /// Initializes the object.
        /// </summary>
        public OpcConnectService(CollectServer server)
        {
            m_configuration = new ApplicationConfiguration();
            m_configuration.ApplicationType = ApplicationType.Client;
            m_configuration.ApplicationName = server.Name;
            m_configuration.ApplicationUri = server.Url;
            m_configuration.ClientConfiguration = new ClientConfiguration();

            Server = server;
            SessionName = server.Code;
            m_CertificateValidation = new CertificateValidationEventHandler(CertificateValidator_CertificateValidation);
            m_endpoints = new Dictionary<Uri, EndpointDescription>();

            ConstructServiceList = new List<ConstructService>();
            CollectionServiceList = new List<CollectReadService>();
            CollectionWriteServiceList = new List<CollectWriteService>();
            //一定要先初始化采集点位，再初始化结构体
            InitCollection();
            InitConstruction();
            InitWriteCollection();
        }
        #region Server

        public void StartServer()
        {
            try
            {
                Task.Factory.StartNew(() => {
                    //连接到服务器
                    Connect(Server.Url, false, 10000).Wait();

                    ListenerServerStatus();
                });
            }
            catch (Exception ex)
            {
                LogHelper.Error("ConnectionService-StartServer:" + ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        public void Stop()
        {
            //停止点位采集
            StopCollect();
            //停止结构体
            StopConstruction();

            //关闭SESSION
            Disconnect();

            ListenerServerStatus();
            //停止服务器状态监听
            Thread.Sleep(Server.Frequency + 100);
            read = false;
            if (ListenerServerStatusThread != null /*&& ListenerServerStatusThread.IsAlive*/) { ListenerServerStatusThread.Abort(); }
        }
        #endregion

        #region Constructors

        /// <summary>
        /// 启动结构体
        /// </summary>
        /// <param name="server"></param>
        public void InitConstruction()
        {
            try
            {
                ConstructServiceList.Clear();
                for (int i = 0; i < Server.Constructions.Count; i++)
                {
                    List<CollectReadService> collectionServices = GetConstructionCollect(Server.Constructions[i]);
                    ConstructService constructService = new ConstructService(this, Server.Constructions[i], collectionServices);
                    ConstructServiceList.Add(constructService);
                }
            }
            catch (Exception ex)
            {
                LogHelper.Error($"启用服务器“{Server.Name}”错误：" + ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        /// <summary>
        /// 获取结构体内所有点位
        /// </summary>
        /// <param name="construction"></param>
        /// <returns></returns>
        private List<CollectReadService> GetConstructionCollect(Construction construction)
        {
            List<CollectReadService> collectionServices = new List<CollectReadService>();
            for (int i = 0; i < construction.KeyValues.Count; i++)
            {
                CollectReadService collectionService = CollectionServiceList.FirstOrDefault(m => m.Collection.Key == construction.KeyValues[i].Key);
                if (collectionService != null)
                {
                    collectionServices.Add(collectionService);
                }
            }
            return collectionServices;
        }
        /// <summary>
        /// 开始监听结构体
        /// </summary>
        public void StartConstruction()
        {
            foreach (var item in ConstructServiceList)
            {
                item.Start();
            }
        }
        /// <summary>
        /// 开始监听结构体
        /// </summary>
        public void ReStartConstruction()
        {
            foreach (var item in ConstructServiceList)
            {
                item.Stop();
                item.Start();
            }
        }
        public void StopConstruction()
        {
            foreach (var item in ConstructServiceList)
            {
                item.Stop();
            }
        }
        #endregion

        #region Collect

        /// <summary>
        /// 开始采集点位数据
        /// </summary>
        /// <param name="server"></param>
        public void InitCollection()
        {
            try
            {
                CollectionServiceList.Clear();
                for (int i = 0; i < Server.Collections.Count; i++)
                {
                    if (Server.Collections[i].ValueType == "Collect")
                    {
                        CollectReadService collectionService = new CollectReadService(this, Server.Collections[i]);
                        CollectionServiceList.Add(collectionService);
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.Error($"启用服务器“{Server.Name}”错误：" + ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        /// <summary>
        /// 开始写业务
        /// </summary>
        private void InitWriteCollection()
        {
            List<Collection> collections = Server.Collections.Where(m => m.ReadType == "Write").ToList();
            foreach (var item in collections)
            {
                CollectWriteService collectionService = new CollectWriteService(this, item);
                CollectionWriteServiceList.Add(collectionService);
            }
        }
        /// <summary>
        /// 开始读取采集点位
        /// </summary>
        public void StartWriter()
        {
            foreach (var item in CollectionWriteServiceList)
            {
                item.StartWrite();
            }
        }
        /// <summary>
        /// 开始读取采集点位
        /// </summary>
        public void StartCollect()
        {
            foreach (var item in CollectionServiceList)
            {
                item.Read();
            }
        }
        /// <summary>
        /// 开始读取采集点位
        /// </summary>
        public void ReStartCollect()
        {
            foreach (var item in CollectionServiceList)
            {
                item.Stop();
                item.Read();
            }
        }

        public void StopCollect()
        {
            foreach (var item in CollectionServiceList)
            {
                item.Stop();
            }
        }
        #endregion


        #region Public Members

        /// <summary>
        /// Default session values.
        /// </summary>
        public static readonly uint DefaultSessionTimeout = 10000;
        public static readonly int DefaultDiscoverTimeout = 10000;
        public static readonly int DefaultReconnectPeriod = 1;
        public static readonly int DefaultReconnectPeriodExponentialBackOff = 10;

        /// <summary>
        /// The name of the session to create.
        /// </summary>
        public string SessionName { get; set; }

        /// <summary>
        /// Gets or sets a flag indicating that the domain checks should be ignored when connecting.
        /// </summary>
        public bool DisableDomainCheck { get; set; }

        /// <summary>
        /// The locales to use when creating the session.
        /// </summary>
        public string[] PreferredLocales { get; set; }

        /// <summary>
        /// The user identity to use when creating the session.
        /// </summary>
        public IUserIdentity UserIdentity { get; set; }

        /// <summary>
        /// The client application configuration.
        /// </summary>
        public ApplicationConfiguration Configuration
        {
            get => m_configuration;

            set
            {
                if (!Object.ReferenceEquals(m_configuration, value))
                {
                    if (m_configuration != null)
                    {
                        m_configuration.CertificateValidator.CertificateValidation -= m_CertificateValidation;
                    }

                    m_configuration = value;

                    if (m_configuration != null)
                    {
                        m_configuration.CertificateValidator.CertificateValidation += m_CertificateValidation;
                    }
                }
            }
        }

        /// <summary>
        /// Creates a new session.
        /// </summary>
        /// <returns>The new session object.</returns>
        public async Task<Session> Connect(
            ITransportWaitingConnection connection,
            EndpointDescription endpointDescription,
            bool useSecurity,
            uint sessionTimeout = 10000)
        {
            // disconnect from existing session.
            InternalDisconnect();

            // select the best endpoint.
            if (endpointDescription == null)
            {
                endpointDescription = CoreClientUtils.SelectEndpoint(m_configuration, connection, useSecurity, DiscoverTimeout);
            }

            EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration);
            ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);


            try
            {
                m_session = await Session.Create(
                    m_configuration,
                    connection,
                    endpoint,
                    false,
                    !DisableDomainCheck,
                    (String.IsNullOrEmpty(SessionName)) ? m_configuration.ApplicationName : SessionName,
                    sessionTimeout,
                    UserIdentity,
                    PreferredLocales);

                // set up keep alive callback.
                m_session.KeepAlive += Session_KeepAlive;
                m_session.SessionClosing += new EventHandler(SessionClosing);

                // set up reconnect handler.
                m_reconnectHandler = new SessionReconnectHandler(true, DefaultReconnectPeriodExponentialBackOff * 1000);

                // raise an event.
                DoConnectComplete(null);
                Status = true;
                var typeSystemLoader = new ComplexTypeSystem(m_session);
                await typeSystemLoader.Load();

                //添加连接完成事件
                if (m_ConnectEvent != null)
                {
                    m_ConnectEvent(SessionName, new EventArgs());
                }
            }
            catch (Exception ex)
            {
                LogHelper.Error("OpcConnectService.Connect:" + ex.Message + "\r\n" + ex.StackTrace);
            }

            // return the new session.
            return m_session;
        }

        /// <summary>
        /// Creates a new session.
        /// </summary>
        /// <returns>The new session object.</returns>
        public async Task<Session> Connect(
            string serverUrl,
            bool useSecurity,
            uint sessionTimeout = 0)
        {
            // disconnect from existing session.
            InternalDisconnect();

            // select the best endpoint.
            var endpointDescription = CoreClientUtils.SelectEndpoint(m_configuration, serverUrl, useSecurity, DiscoverTimeout);
            var endpointConfiguration = EndpointConfiguration.Create(m_configuration);
            var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);

            try
            {
                //m_session = await Session.Create(m_configuration, (ITransportWaitingConnection)null, endpoint, false, false, m_configuration.ApplicationName, 15000, UserIdentity, PreferredLocales, default(CancellationToken));
                m_session = await Session.Create(
                    m_configuration,
                    endpoint,
                    false,
                    false,
                    (String.IsNullOrEmpty(SessionName)) ? m_configuration.ApplicationName : SessionName,
                    sessionTimeout == 0 ? DefaultSessionTimeout : sessionTimeout,
                    UserIdentity,
                PreferredLocales);

                // set up keep alive callback.

                m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);
                m_session.ReturnDiagnostics = DiagnosticsMasks.SymbolicIdAndText;
                // set up reconnect handler.
                m_reconnectHandler = new SessionReconnectHandler(true, DefaultReconnectPeriodExponentialBackOff * 1000);

                // raise an event.
                DoConnectComplete(null);

                Status = true;
                var typeSystemLoader = new ComplexTypeSystem(m_session);
                await typeSystemLoader.Load();

                //添加连接完成事件
                if (m_ConnectEvent != null)
                {
                    m_ConnectEvent(SessionName, new EventArgs());
                }
            }
            catch (Exception ex)
            {
                LogHelper.Error("OpcConnectService.Connect:" + ex.Message + "\r\n" + ex.StackTrace);
            }

            // return the new session.
            return m_session;
        }

        /// <summary>
        /// Creates a new session.
        /// </summary>
        /// <param name="serverUrl">The URL of a server endpoint.</param>
        /// <param name="useSecurity">Whether to use security.</param>
        /// <returns>The new session object.</returns>
        public Task<Session> ConnectAsync(
            string serverUrl,
            bool useSecurity = false,
            uint sessionTimeout = 0
            )
        {
            Status = false;

            return Connect(serverUrl, useSecurity, sessionTimeout);
        }

        /// <summary>
        /// Create a new reverse connection.
        /// </summary>
        /// <param name="connection"></param>
        /// <param name="useSecurity"></param>
        public async Task<Session> ConnectAsync(
            ITransportWaitingConnection connection,
            bool useSecurity,
            int discoverTimeout = -1,
            uint sessionTimeout = 0
            )
        {
            if (connection.EndpointUrl == null)
            {
                throw new ArgumentException("Endpoint URL is not valid.");
            }

            EndpointDescription endpointDescription = null;

            if (!m_endpoints.TryGetValue(connection.EndpointUrl, out endpointDescription))
            {
                // Discovery uses the reverse connection and closes it
                // return and wait for next reverse hello
                endpointDescription = CoreClientUtils.SelectEndpoint(m_configuration, connection, useSecurity, discoverTimeout);
                m_endpoints[connection.EndpointUrl] = endpointDescription;
                return null;
            }

            return await Connect(connection, endpointDescription, useSecurity, sessionTimeout);
        }

        /// <summary>
        /// Opc Session 关闭事件
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="e"></param>
        private void SessionClosing(object obj, EventArgs e)
        {
            Status = false;
        }

        /// <summary>
        /// Disconnects from the server.
        /// </summary>
        public Task DisconnectAsync()
        {
            Status = false;
            read = false;
            return Task.Run(() => InternalDisconnect());
        }

        /// <summary>
        /// Disconnects from the server.
        /// </summary>
        private void InternalDisconnect()
        {
            Status = false;
            // stop any reconnect operation.
            if (m_reconnectHandler != null)
            {
                m_reconnectHandler.Dispose();
                m_reconnectHandler = null;
            }

            // disconnect any existing session.
            if (m_session != null)
            {
                m_session.KeepAlive -= Session_KeepAlive;
                m_session.Close(10000);
                m_session = null;
            }

            // raise an event.
            DoConnectComplete(null);
        }

        /// <summary>
        /// Disconnects from the server.
        /// </summary>
        public void Disconnect()
        {
            Status = false;
            read = false;
            // stop any reconnect operation.
            InternalDisconnect();
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Raises the connect complete event on the main GUI thread.
        /// </summary>
        private void DoConnectComplete(object state)
        {
            if (m_ConnectComplete != null)
            {
                m_ConnectComplete(this, null);
            }
        }
        #endregion

        #region Event Handlers
        private delegate void UpdateStatusCallback(bool error, DateTime time, string status, params object[] arg);

        /// <summary>
        /// Handles a keep alive event from a session.
        /// </summary>
        private void Session_KeepAlive(ISession session, KeepAliveEventArgs e)
        {
            try
            {
                // check for events from discarded sessions.
                if (!Object.ReferenceEquals(session, m_session))
                {
                    return;
                }

                // start reconnect sequence on communication error.
                if (ServiceResult.IsBad(e.Status))
                {
                    Status = false;
                    if (ReconnectPeriod <= 0)
                    {
                        return;
                    }

                    var state = m_reconnectHandler.BeginReconnect(m_session, ReconnectPeriod * 1000, Server_ReconnectComplete);
                    if (state == SessionReconnectHandler.ReconnectState.Triggered)
                    {
                        m_ReconnectStarting?.Invoke(this, e);
                    }

                    return;
                }

                Status = true;

                // raise any additional notifications.
                if (m_KeepAliveComplete != null)
                {
                    m_KeepAliveComplete(this, e);
                }
            }
            catch (Exception ex)
            {
                LogHelper.Error("OpcConnectService.Session_KeepAlive:" + ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        /// <summary>
        /// Handles a reconnect event complete from the reconnect handler.
        /// </summary>
        private void Server_ReconnectComplete(object sender, EventArgs e)
        {
            try
            {
                // ignore callbacks from discarded objects.
                if (!Object.ReferenceEquals(sender, m_reconnectHandler))
                {
                    return;
                }

                // only apply session if reconnect was required
                if (m_reconnectHandler.Session != null)
                {
                    if (!ReferenceEquals(m_session, m_reconnectHandler.Session))
                    {
                        var session = m_session;
                        session.KeepAlive -= Session_KeepAlive;
                        m_session = m_reconnectHandler.Session as Session;
                        m_session.KeepAlive += Session_KeepAlive;
                        Utils.SilentDispose(session);
                    }
                }

                // raise any additional notifications.
                if (m_ReconnectComplete != null)
                {
                    m_ReconnectComplete(this, e);
                }
            }
            catch (Exception ex)
            {
                LogHelper.Error(ex.Message + "\r\n" + ex.StackTrace);
            }
        }

        /// <summary>
        /// Handles a certificate validation error.
        /// </summary>
        private void CertificateValidator_CertificateValidation(CertificateValidator sender, CertificateValidationEventArgs e)
        {
            try
            {
                if (!m_configuration.SecurityConfiguration.AutoAcceptUntrustedCertificates)
                {
                    e.Accept = false;
                }
                else
                {
                    e.Accept = true;
                }
            }
            catch (Exception ex)
            {
                LogHelper.Error("OpcConnectService.CertificateValidator_CertificateValidation:" + ex.Message + "\r\n" + ex.StackTrace);
            }
        }
        #endregion

        bool read = true;
        Thread ListenerServerStatusThread;
        /// <summary>
        /// 监控OPC CLIENT 服务运行状态
        /// </summary>
        public void ListenerServerStatus()
        {
            read = false;
            if (ListenerServerStatusThread != null/* && ListenerServerStatusThread.IsAlive*/) { ListenerServerStatusThread.Abort(); }
            read = true;
            ListenerServerStatusThread = new Thread(() => {
                while (read)
                {
                    try
                    {
                        if (m_ConnectStatusEvent != null)
                        {
                            if (Session == null)
                            {
                                Server.Status = "false";
                            }
                            else
                            {
                                Server.Status = Session.Connected.ToString();
                            }
                            m_ConnectStatusEvent(Server, new EventArgs());
                        }
                    }
                    catch (Exception ex)
                    {
                        Status = false;
                    }
                    Thread.Sleep(Server.Frequency);
                }
            });
            ListenerServerStatusThread.Start();
        }

        public object GetInstances()
        {
            Type type = typeof(CollectReadService); // 获取要查询的类型

            Assembly assembly = Assembly.GetExecutingAssembly(); // 获取当前程序集

            var instances = from t in assembly.GetTypes() // 遍历程序集中的每个类型
                            where t == type || (type != null && type.IsAssignableFrom(t)) // 判断该类型与目标类型相同或者是其子类/接口
                            select Activator.CreateInstance(t); // 创建该类型的实例并返回
            return instances;
        }
    }
}
